package function;

import java.util.ArrayList;

import java.util.Stack;

public class Parser {
	private static final String[] FUNCTIONS = { "sin", "cos", "tan", "exp",
			"log", "ln" };
	private static final String OPERATORS = "+-*/^";
	private static Stack<Object> stackRPN = new Stack<Object>();
	private static Stack<Function> stackAnswer = new Stack<Function>();

	private static String minusSolving(String st) {
		boolean b = false;
		if (st.charAt(0) == '-' || st.charAt(0) == '+') {
			int i = 1;
			while (i < st.length() && st.charAt(i) != '+'
					&& st.charAt(i) != '-' && st.charAt(i) != '*'
					&& st.charAt(i) != '/' && st.charAt(i) != '^') {
				i++;
			}
			st = "(0" + st.charAt(0) + st.substring(1, i) + ")"
					+ st.substring(i, st.length());
		}
		String inf = st;
		for (int i = 0; i < st.length(); i++) {
			if (st.charAt(i) == '-' || st.charAt(i) == '+') {
				if (st.charAt(i - 1) != ')'
						&& ((st.charAt(i - 1) < '0' || st.charAt(i - 1) > '9')
								&& (st.charAt(i - 1) != 'x')
								&& (st.charAt(i - 1) != 'n')
								&& (st.charAt(i - 1) != 's') && (st
								.charAt(i - 1) != 'p' && (st.charAt(i - 1) != 'g')))) {
					int j = i + 1;
					while (j < st.length() && st.charAt(j) != '+'
							&& st.charAt(j) != '-' && st.charAt(j) != '*'
							&& st.charAt(j) != '/' && st.charAt(i) != '^'
							&& st.charAt(j) != '(' && st.charAt(j) != ')') {
						j++;
					}
					inf = st.substring(0, i) + "(0" + st.charAt(i)
							+ st.substring(i + 1, j) + ")"
							+ st.substring(j, st.length());
				}
			}
			st = inf;
			if (i > 0) {
				if (st.charAt(i) == '+' || st.charAt(i) == '-'
						|| st.charAt(i) == '*' || st.charAt(i) == '^'
						|| st.charAt(i) == '/' || st.charAt(i) == '('
						|| st.charAt(i) == ')') {
					b = true;
				}
			}
		}
		if (b == false) {
			inf = inf + "+0";
		}
		return inf;
	}

	private static boolean validate(String infix) throws Exception {
		for (int i = 0; i < infix.length(); i++) {
			if (infix.charAt(i) != '(' && infix.charAt(i) != ')'
					&& (infix.charAt(i) < '0' || infix.charAt(i) > '9')
					&& (infix.charAt(i) != 'x') && (infix.charAt(i) != 'n')
					&& (infix.charAt(i) != 's') && infix.charAt(i) != 'i'
					&& infix.charAt(i) != 'c' && infix.charAt(i) != 'o'
					&& infix.charAt(i) != 'l' && infix.charAt(i) != 'g'
					&& infix.charAt(i) != 'a' && infix.charAt(i) != 'e'
					&& infix.charAt(i) != 'p' && infix.charAt(i) != 't'
					&& infix.charAt(i) != '-' && infix.charAt(i) != '/'
					&& infix.charAt(i) != '*' && infix.charAt(i) != '+'
					&& infix.charAt(i) != '^' && infix.charAt(i) != '.') {
				throw new Exception(
						"Invalid Input (There is not allowed characters) ! ");
			}
		}
		Stack<Object> s = new Stack<Object>();
		infix = minusSolving(infix);
		ArrayList<Object> function = toArray(infix);
		for (int i = 0; i < function.size(); i++) {
			if (function.get(i).equals('(')) {
				s.push('(');
			} else {
				if (function.get(i).equals(')')) {
					s.pop();
				} else {
					if (function.get(i).equals("sin")
							|| function.get(i).equals("cos")
							|| function.get(i).equals("exp")
							|| function.get(i).equals("tan")
							|| function.get(i).equals("log")
							|| function.get(i).equals("ln")) {
						if (i < function.size() - 1
								&& !function.get(i + 1).equals('('))
							return false;
						if (i < function.size() - 2
								&& function.get(i + 2).equals(')'))
							throw new Exception("Empty parentheses ! ");
					} else {
						boolean b = false;
						try {
							Double.parseDouble(function.get(i) + "");
						} catch (Exception e) {
							b = true;
						}
						if (b == true && !function.get(i).equals("sin")
								&& !function.get(i).equals("cos")
								&& !function.get(i).equals("exp")
								&& !function.get(i).equals("tan")
								&& !function.get(i).equals("log")
								&& !function.get(i).equals("ln")
								&& !function.get(i).equals('-')
								&& !function.get(i).equals('+')
								&& !function.get(i).equals('*')
								&& !function.get(i).equals('/')
								&& !function.get(i).equals('^')
								&& !function.get(i).equals("x")) {
							throw new Exception(
									"Invalid Input (There is not allowed Functions)! ");
						}
					}
				}
			}
		}
		if (s.size() == 0)
			return true;
		return false;
	}

	public static Object[] ToPostfix(String infix) throws Exception {
		if (validate(infix) == true) {
			Stack<Object> s = new Stack<Object>();
			int p = 0;
			infix = minusSolving(infix);
			ArrayList<Object> function = toArray(infix);
			Object[] post = new Object[DeleteParan(function)];
			try {
				for (int i = 0; i < function.size(); i++) {
					if (function.get(i).equals('(')) {
						s.push('(');
					} else {
						if (function.get(i).equals(')')) {
							while ((Character) s.peek() != '(') {
								post[p] = s.pop();
								p++;
							}
							s.pop();
							if (s.size() > 0
									&& (s.peek().equals("sin")
											|| s.peek().equals("cos")
											|| s.peek().equals("exp")
											|| s.peek().equals("tan")
											|| s.peek().equals("log") || s
											.peek().equals("ln"))) {
								post[p] = s.pop();
								p++;
							}
						} else {
							if (function.get(i).equals("sin")
									|| function.get(i).equals("cos")
									|| function.get(i).equals("exp")
									|| function.get(i).equals("tan")
									|| function.get(i).equals("log")
									|| function.get(i).equals("ln")) {
								s.push(function.get(i));
							} else {
								if (function.get(i).equals('+')
										|| function.get(i).equals('-')
										|| function.get(i).equals('*')
										|| function.get(i).equals('/')
										|| function.get(i).equals('^')) {
									if (s.isEmpty()
											|| Priority(function.get(i)) > Priority(s
													.peek())) {
										s.push(function.get(i));
									}

									else {

										while (s.size() != 0
												&& (Character) s.peek() != '('
												&& Priority(function.get(i)) <= Priority(s
														.peek())) {

											post[p] = s.pop();
											p++;

										}

										s.push(function.get(i));

									}

								} else {
									if (function.get(i) != "(")
										post[p] = function.get(i);
									p++;
								}

							}
						}
					}
				}

				for (int j = p; s.size() != 0; j++) {

					post[j] = s.pop();
				}
				return post;
			} catch (Exception e) {
				return null;
			}
		} else
			throw new Exception("Check parentheses !");

	}

	private static ArrayList<Object> toArray(String function) {
		ArrayList<Object> i = new ArrayList<Object>();
		String s = "";
		int k = 0;
		for (int j = 0; j < function.length(); j++) {
			if (function.charAt(j) == '(' || function.charAt(j) == ')') {
				i.add(function.charAt(j));
			} else {
				k = j;
				while (k < function.length() && function.charAt(k) != '+'
						&& function.charAt(k) != '-'
						&& function.charAt(k) != '*'
						&& function.charAt(k) != '/'
						&& function.charAt(k) != ')'
						&& function.charAt(k) != '('
						&& function.charAt(k) != '^') {
					s += function.charAt(k);
					k++;
				}
				if (function.charAt(k - 1) != ')') {
					i.add(s);
				}
				if (k < function.length()) {
					i.add(function.charAt(k));
				}
				j = k;
				s = "";
			}
		}
		return i;
	}

	public static String toString(Object[] o) {
		String s = "";
		try {
			for (int i = 0; i < o.length; i++) {
				s += o[i];
			}
			return s;
		} catch (Exception e) {
			return "Invalid input";
		}
	}

	private static int DeleteParan(ArrayList<Object> a) {
		int length = 0;
		int i = 0;
		while (i < a.size()) {
			if (a.get(i).equals('(') || a.get(i).equals(')')) {
				i++;
			} else {
				length++;
				i++;
			}
		}
		return length;
	}

	private static int Priority(Object s) {
		if (s.equals('^'))
			return 3;
		else if (s.equals('*') || s.equals('/'))
			return 2;
		else if (s.equals('+') || s.equals('-'))
			return 1;
		else
			return 0;
	}

	public static Function toFunction(String s) throws Exception {
		Object[] ab = ToPostfix(s);
		stackRPN.clear();
		for (int i = ab.length - 1; i >= 0; i--) {
			stackRPN.push(ab[i]);
		}
		stackAnswer.clear();
		while (!stackRPN.empty()) {
			Object token = stackRPN.pop();
			if (token.toString().equalsIgnoreCase("x"))
				stackAnswer.push(new Variable());
			else if (isNumber((String) (token + ""))) {
				stackAnswer.push(new Constant(Double
						.parseDouble((String) token)));
			} else if (isOperator((String) (token + ""))) {
				Function a1 = stackAnswer.pop();
				Function a2 = stackAnswer.pop();
				if (token.equals('+'))
					stackAnswer.push(new ADD(a2, a1));
				else if (token.equals('-'))
					stackAnswer.push(new ADD(a2, a1.invert()));
				else if (token.equals('*'))
					stackAnswer.push(new Multiply(a2, a1));
				else if (token.equals('/'))
					stackAnswer.push(new Division(a2, new Power(a1,
							new Constant(1))));
				else if (token.equals('^'))
					stackAnswer.push(new Power(a2, a1));
			} else if (isFunction((String) token)) {
				Function a = stackAnswer.pop();
				if (token.equals("sin"))
					stackAnswer.push(new Sin(a));
				else if (token.equals("cos"))
					stackAnswer.push(new Cos(a));
				else if (token.equals("tan"))
					stackAnswer.push(new Division(new Sin(a), new Power(
							new Cos(a), new Constant(1))));
				else if (token.equals("exp"))
					stackAnswer.push(new Power(new Constant(Math.E), a));
				else if (token.equals("log"))
					stackAnswer.push(new Log(10, a));
				else if (token.equals("ln"))
					stackAnswer.push(new Log(Math.E, a));
			}
		}
		if (stackAnswer.size() > 1) {
			throw new Exception("Some operator is missing");
		}
		return stackAnswer.pop();
	}

	private static boolean isNumber(String token) {
		try {
			Double.parseDouble(token);
		} catch (Exception e) {
			return false;
		}
		return true;
	}

	private static boolean isFunction(String token) {
		for (String item : FUNCTIONS)
			if (item.equals(token))
				return true;
		return false;
	}

	private static boolean isOperator(String token) {
		return OPERATORS.contains(token);
	}
}
